package il.ac.tau.cg.ex3.math.shapes;

import il.ac.tau.cg.ex3.math.Matrix;
import il.ac.tau.cg.ex3.math.Params;
import il.ac.tau.cg.ex3.math.Point;
import il.ac.tau.cg.ex3.math.Vector;
import il.ac.tau.cg.ex3.utils.octree.BoundingBox;

import java.util.SortedSet;
import java.util.TreeSet;

public class Rectangle extends Shape
{
	private Point p0;
	private Point p1;
	private Point p2;
	
	/*
	 *    p0------p1
	 *    |       |
	 *    |       |
	 *    p2------p3 = p1 + (p2 - p0)
	 */
	public Rectangle(Point p0, Point p1, Point p2)
	{
		this.p0 = p0;
		this.p1 = p1;
		this.p2 = p2;
	}

	public Point[][] createGrid(int dimension)
	{
		Point[][] grid = new Point[dimension][dimension];
		
		Vector v1 = new Vector(p0, p1).scale(1.0 / dimension);
		Vector v2 = new Vector(p0, p2).scale(1.0 / dimension);
		
		for (int i = 0; i < dimension; i++)
		{
			for (int j = 0; j < dimension; j++)
			{
				grid[i][j] = p0.add(v1.scale(0.5 + i)).add(v2.scale(0.5 + j));
			}
		}
		
		return grid;
	}
	
	public void setP0(Point p0)
	{
		this.p0 = p0;
	}
	
	public Point getP0()
	{
		return p0;
	}
	
	public void setP1(Point p1)
	{
		this.p1 = p1;
	}
	
	public Point getP1()
	{
		return p1;
	}
	
	public void setP2(Point p2)
	{
		this.p2 = p2;
	}
	
	public Point getP2()
	{
		return p2;
	}
	
	@Override
	public SortedSet<Double> intersect(Point origin, Vector direction)
	{
		SortedSet<Double> result = new TreeSet<Double>();
		
		double x1 = -direction.getX();
		double y1 = -direction.getY();
		double z1 = -direction.getZ();
		
		double x2 = p1.getX() - p0.getX();
		double y2 = p1.getY() - p0.getY();
		double z2 = p1.getZ() - p0.getZ();
		
		double x3 = p2.getX() - p0.getX();
		double y3 = p2.getY() - p0.getY();
		double z3 = p2.getZ() - p0.getZ();
		
		Matrix m = new Matrix(x1, y1, z1, x2, y2, z2, x3, y3, z3).inverse();
		
		if (m == null)
		{
			return result;
		}
		
		double x = origin.getX() - p0.getX();
		double y = origin.getY() - p0.getY();
		double z = origin.getZ() - p0.getZ();
		
		Vector r = m.mul(new Vector(x, y, z));
		
		double t = r.getX();
		double u = r.getY();
		double v = r.getZ();
		
		if ((t < 0.0) || (u < 0.0) || (u > 1.0) || (v < 0.0) || (v > 1.0))
		{
			return result;
		}
		
		result.add(t);
		
		return result;
	}

	@Override
	public Vector getNormal(Point p)
	{
		return new Vector(p0, p1).cross(new Vector(p0, p2)).normalize();
	}

	@Override
	public Params getParams(Point p)
	{
		Vector vec = new Vector(p0, p);
		
		Vector v1 = new Vector(p0, p1);
		Vector v2 = new Vector(p0, p2);
		
		double u = vec.dot(v1) / Math.pow(v1.length(), 2.0);
		double v = vec.dot(v2) / Math.pow(v2.length(), 2.0);
		
		u = Math.min(Math.max(u, 0.0), 1.0);
		v = Math.min(Math.max(v, 0.0), 1.0);
		
		return new Params(u, v);
	}

	@Override
	public BoundingBox getBoundingBox()
	{
		Point p3 = p1.add(new Vector(p0, p2));
		
		double minX = Math.min(Math.min(p0.getX(), p1.getX()), Math.min(p2.getX(), p3.getX()));
		double maxX = Math.max(Math.max(p0.getX(), p1.getX()), Math.max(p2.getX(), p3.getX()));
		
		double minY = Math.min(Math.min(p0.getY(), p1.getY()), Math.min(p2.getY(), p3.getY()));
		double maxY = Math.max(Math.max(p0.getY(), p1.getY()), Math.max(p2.getY(), p3.getY()));
		
		double minZ = Math.min(Math.min(p0.getZ(), p1.getZ()), Math.min(p2.getZ(), p3.getZ()));
		double maxZ = Math.max(Math.max(p0.getZ(), p1.getZ()), Math.max(p2.getZ(), p3.getZ()));
		
		return new BoundingBox(minX, maxX, minY, maxY, minZ, maxZ);
	}
}
